- #### فصل ششم:
- #### توابع دارای خروجی (fruitful functions):
- #### ۶.۱ برگرداندن مقدار:
- #### ۶.۲ توسعه تکاملی
- #### فرمول قضیه فیثاغورث
- #### ۶.۳ ترکیب
- #### ۶.۴ توابع بولی
- #### ۶.۵ بازگشت بیشتر
- #### 6.6 leap of faith اعتماد عمیق
- #### 6.7 یک مثال بیشتر
- #### 6.8 کنترل نوع
- ####
- #### 6.9 خطایابی
- #### 6.10 فهرست لغات
- #### 6.11 تمرینات
#### فصل ششم:
#### توابع دارای خروجی (fruitful functions):
بسیاری از توابع پایتونی که ما استفاده می کنیم (مثل تابع math) مقداری را برای بازگشت تولید میکنند. اما توابعی که ما تا اینجا نوشتیم همگی از نوع void هستند: توابعی که اثری مانند چاپ یک مقدار یا (moving a turtle)دارند اما نمی توانند مقداری را برگردانند. در این فصل شما نحوه نوشتن توابع دارای خروجی را خواهید آموخت.
#### ۶.۱ برگرداندن مقدار:
فراخوانی توابع، یک مقدار برای برگرداندن تولید میکند که ما معمولا آنرا به یک متغیر اختصاص میدهیم یا به عنوان قسمتی از یک عبارت استفاده میکنیم.
e = math.exp(1.0)
height = radius * math.sin(radians)
توابعی که ما تا به حال نوشتیم همگی توابع نابارور(void) بودند، به بیان ساده تر هیچ مقداری را بازنمی گردانند یا دقیقا None را برمی گرداند.
در این فصل، ما (بلاخره!) تصمیم گرفتیم که توابع دارای خروجی بنویسیم. اولین مثال تابعی است که مساحت دایره ای که شعاع آن داده شده را محاسبه میکند.
def area(radius):
a = math.pi * radius**2
return a
ما پیش از این هم عبارت return را دیده بودیم اما در توابع دارای خروجی دستور return حاوی یک سری عبارات است. این دستور به این معناست که: "بلافاصله از این تابع برگرد (خارج شو) و از عبارت مقابل هم به عنوان مقدار بازگشتی استفاده کن" این عبارت میتوانند به طور دلخواه پیچیده(تلفیقی)باشند، بنابراین ما میتوانیم آنها را به اختصار بنویسیم.
def area(radius):
return math.pi * radius**2
به عبارت دیگر، عبارات موقتی مانند a می توانند خطایابی را ساده تر کنند.
گاهی بهتر است از چند عبارت return استفاده کنیم به عنوان مثال یک return در هر عبارت شرطی:
def absolute_value(x):
if x < 0:
return -x
else:
return x
از آنجایی که این عبارات return در شرط های متناوب هستند، فقط یکی از آنها اجرا خواهد شد.
به محض اینکه یکی از عبارات return اجرا شود، تابع بدون اجرای عبارات بعدی به کار خود پایان میدهد. کدی که بعد از عبارت return یا هرجای دیگری که جریان اجرا هیچوقت نمیتواند به آن دسترسی پیداکند نوشته شده کد بلااستفاده (dead code)نامیده میشود.
در توابع دارای خروجی، بهتر است که مطمين شوید تمام راه های ممکن در برنامه به یک عبارت return ختم می شود. برای مثال:
def absolute_value(x):
if x < 0 :
return -x
if x > 0:
return x
این تابع اشتباه است زیرا وقتی که مقدار x برابر با 0 باشد ،هیچ کدام از شروط درست نیستند و تابع بدون دسترسی به عبارت return تمام می شود. اگر جریان اجرای برنامه به انتهای تابع برسد، مقدار return None خواهد بود که برابر با مقدار مطلق 0 نیست.
>>>absolute_value(0)
None
در هرحال پایتون یک تابع درون سازی شده به نام abs ارايه داده که مقدار قدر مطلق اعداد را برمیگرداند.
برای تمرین،تابعی بنویسید که دو مقدار x , y را میگیرد و 1 را برمیگرداند اگر x > y ,مقدار 0 را اگر x == 0 و مقدار -1 را اگر x < y.
#### ۶.۲ توسعه تکاملی
با نوشتن توابع بزرگتر، شما باید وقت بیشتری را صرف اشکال زدایی آن کنید.
برای مقابله با برنامه هایی که پیچیدگی فزاینده ای دارند ممکن است بخواهید فرآیند توسعه تکاملی را امتحان کنید. هدف توسعه تکاملی جلوگیری از جلسات طولانی خطایابی برنامه ها با استفاده از اضافه و آزمایش کردن مقدار کمی از کد در هر بار است.
به عنوان مثال، فرض کنید شما می خواهید فاصله بین دو نقطه با مختصات (x1,y1) و (x2,y2) را به دست آورید.طبق قضیه فیثاغورث فاصله عبارت است از:
#### فرمول قضیه فیثاغورث
در مرحله اول در نظر بگیرید تابع مسافت در پایتون چگونه باید باشد. به عبارت دیگر، ورودی ها (پارامترها) و خروجی ها (مقادیر بازگشتی) چیست؟
بلافاصله شما می توانید طرح کلی تابع را بنویسید:
def distance(x1, y1, x2, y2):
return 0.0
واضح است که این نسخه مسافت را محاسبه نخواهد کرد و همیشه مقدار صفر را برمیگرداند. اما از نظر نحو درست است و اجرا می شود بدین معنا که شما می توانید آن را قبل از پیچیده کردن آزمایش کنید .
برای آزمایش تابع جدید، آن را با آرگومان های نمونه فراخوانی کنید:
>>>destance(1,2,3,4)
0.0
در مثال بالا مسافت افقی ۳ و مسافت عمودی ۴ خواهد بود که حاصل ۵ یا همان وتر مثلث به اضلاع ۳و ۴ و ۵ می شود.در هنگام آزمایش یک تابع بهتر است پاسخ درست را بدانید.
تا اینجا مطمين شدیم که تابع از نظر نحوی درست کار می کند و می توانیم اضافه کردن کد به بدنه تابع را آغاز کنیم. راه حل معقول بعدی این است که اختلاف x2-x1 و y2-y1 را محاسبه کنید. نسخه بعدی این مقادیر را در متغیرهای موقتی ذخیره میکند و آنها را چاپ میکند.
def distance(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
print (‘dx is’, dx)
print (dy is’, dy)
return 0.0
اگر تابع درست کار کند باید 'dx is 3' و 'dy is 4' را نشان دهد. اگر اینچنین بود میفهمیم که تابع آرگومان ها را می گیرد و محاسبات ابتدایی را درست انجام میدهد و اگر اینچنین نبود تنها چند خط وجود دارد که باید چک شود.
حال ما مجموع مربع متغیرهای dx و dy را محاسبه میکنیم.
def distance(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
dsquared = dx**2 + dy**2
print (‘dsquared is: ‘, dsquared)
return 0.0
دوباره نیاز است که شما بازهم برنامه را اجرا کرده و خروجی ها را چک کنید( که باید ۲۵ باشد). در انتها شما میتوانید از تابع math.sqrt برای محاسبه و برگرداندن نتیجه استفاده کنید.
def distance(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
dsquared = dx**2 + dy**2
result = math.sqrt(dsuared)
return result
اگر این نسخه درست کار کند که کار شما به پایان رسیده است در غیر اینصورت ممکن است بخواهید مقدار متغیر result را قبل از دستور return چاپ کنید.
نسخه نهایی تابع بعد از اجرا هیچ چیزی را نمایش نمی دهد و تنها یک مقدار را برمیگرداند. دستورهای print که ما حین نوشتن برنامه استفاده کردیم برای خطایابی برنامه مفید هستند اما وقتی تابع درست کار کرد باید آنها را حذف کنید. کدهای اینچنینی داربست نامیده می شوند زیرا برای ساختن برنامه ها مفید هستند اما جزء محصول نهایی نیستند.
در ابتدا شما باید یک یا دو خط کد را در هر بار اضافه کنید. زمانی که تجربه بیشتری را به دست آوردید ممکن است اقدام به نوشتن و اشکال زدایی تکه کدهای بزرگتری کنید. در هر صورت توسعه تکاملی می تواند در زمان خطایابی صرفه جویی بسیار کند.
جنبه های کلیدی این فرآیند عبارتند از:
- ساخت یک برنامه را آغاز کنید و تغییرات تکاملی کوچکی را اعمال کنید. در هر نقطه اگر به خطایی برخوردید ایده خوبی در مورد دلیل خطا خواهید داشت.
- از متغیرها برای نگهداری مقادیر واسط استفاده کنید تا بتوانید آن ها را نمایش دهید و چک کنید.
- وقتی برنامه درست کار کرد، ممکن است بخواهید بعضی از متغیرهای داربستی را حذف کنید یا چند عبارت دستوری را به عبارات ترکیبی تبدیل کنید، البته اگر خوانایی برنامه را مشکل نکند.
برای تمرین، از روش توسعه تکاملی برای نوشتن تابعی به نام «وتر» استفاده کنید که طول وتر مثلث قایم الزاویه ای که طول دو ضلع آن به عنوان آرگومان داده شده است، استفاده کنید. حالت های فرآیند توسعه را ذخیره کنید.
#### ۶.۳ ترکیب
همانطور که انتظار دارید شما میتوانید یک تابع را از داخل تابع دیگر فراخوانی کنید. به عنوان مثال ما یک تابع خواهیم نوشت که دونقطه، یکی به عنوان مرکز دایره و دیگری روی محیط آن و مساحت آن را محاسبه کند.
فرض کنید مرکز دایره در متغیرهای xc و yc و نقطه روی محیط در متغیر های xp و yp ذخیره شود. مرحله اول این است که شعاع دایره را حساب کنیم که همان فاصله بین دو نقطه داده شده است. ما قبلا یک تابع distance نوشتیم که این کار را انجام میدهد.
radius = distance(xc, yc, xp, yp)
در مرحله بعدی هم مساحت دایره را با آن شعاع بدست آمده حساب کنیم که آن تابع را هم نوشتیم:
result = area(radius)
با جمع بندی مراحل بالا در یک تابع، داریم:
def circle_area(xc, yc, xp, yp):
radius = distance(xc, yc, xp, yp)
result = area(radius)
return result
متغیرهای موقتی radius و result در توسعه و خطایابی برنامه مفید هستند و زمانیکه برنامه به درستی اجرا شد، میتوانیم آن را مختصر کنیم.
def circle_area(xc, yc, xp, yp):
return area(distance(xc, yc, xp, yp))
#### ۶.۴ توابع بولی
توابع میتوانند مقادیر بولی را برگردانند، که اغلب برای پنهان کردن آزمایش های پیچیده داخل توابع مناسب هستند، برای مثال:
def is_divisible(x,y):
if x % y == 0:
return True
else:
return False
معمولا توابع بولی را طوری نام گذاری میکنند که شبیه به سوالات بله و خیر باشد، is_divisible مقادیر True یا False را برمیگرداند که نشان می دهد آیا y مضرب x است یا خیر.
مثال:
>>>is_divisible(6,4)
Flase
>>>is_divisble(6,3)
True
حاصل عملگر == یک مقدار بولی خواهد بود،بنابراین میتوانیم تابع را مختصر تر کنیم و مستقیما از دستور return استفاده کنیم:
def is_divisible(x, y):
return x % y == 0
توابع بولی اغلب در دستورات شرطی استفاده می شوند:
if is_divisible(x,y):
print (‘x is divisible by y’)
این ممکن است شما را وسوسه کرده باشد که چیزی شبیه به این بنویسید:
if is_divisible(x,y) == True:
print (‘x is divisible by y’)
اما مقایسه اضافی نیازی نیست.
برای تمرین، تابعی به نام is_between(x,y,z) بنویسید که True را برمیگرداند اگر x<y<z و در غبر اینصورت False
#### ۶.۵ بازگشت بیشتر
ما تا به اینجا تنها زیر مجموعه کوچکی از پایتون را پوشش دادیم اما شاید جالب باشد که بدانید این زیر مجموعه خود یک زبان کامل برنامه نویسی است. بدین معنا که هرچیز قابل محاسبه ای را میتوان با این زبان بیان کرد. هر برنامه ای که تا به حال نوشته شده را میتوان با همین ویژگی هایی که تا به حال از پایتون آموخته اید بازنویسی کرد (در واقع، ممکن است شما نیاز به دستوراتی برای کنترل دستگاه ها از قبیل موس، دیسک و … داشته باشید)
اثبات این ادعا هم تمرین کوچک اما پر اهمیتی است که اولین بار توسط آلن تورینگ یکی از دانشمندان علوم کامپیوتر (ممکن است برخی او را ریاضی دان بدانند اما بسیاری از دانشمندان اولیه علوم کامپیوتر در ابتدا ریاضیدان بوده اند ) مطرح شد که تز تورینگ نامیده می شود.برای توضیحات کاملتر (دقیق تر) درباره تز تورینگ من کتاب مایکل سیپسر به نام «معرفی تیوری محاسبات» را پیشنهاد میکنم.
برای ایده دادن به شما در مورد کارهایی که می توانید با ابزارهایی که تا اینجا آموخته اید، تعدادی توابع از پیش تعریف شده ریاضی را به صورت بازگشتی محاسبه میکنیم. تعریف بازگشتی همانند تعریف دایره وار است، در واقع تعریف شامل همان چیزی است که قرار است تعریف شود و حقیقتا تعریف دایره وار خیلی مفید نیست.
خوشحال:برای توصیف شرایط کسی به کار میرود که خوشحال باشد.
vorpal: An adjective used to describe something that is vorpal.
اگر شما چنین تعریفی را در دیکشنری ببنید ممکن است اذیت شوید. به عبارت دیگر اگر شما به دنبال تعریف تابع فاکتوریل بگردید میبینید که آن را با علامت ! نشان می دهند، ممکن است چیزی شبیه به این را مشاهده کنید:
0! = 1
n! = n(n-1)!
این تعریف بیانگر این است که فاکتوریل ۰ میشود ۱ و فاکتوریل هر عدد دیگری برابر است با حاصلضرب آن عدد با عددی که یک واحد از خودش کوچکتر است.
بنابراین ۳! برابرست با ۳ بار ۲!، که می شود ۲ بار ۱!، که می شود ۱ بار ۰!. حال همه اینها را کنار یکدیگر قرار می دهیم که می شود ۳ بار ۲ بار ۱ بار ۱ برابر با ۶.
اگر شما بتوانید یک تعریف بازگشتی از چیزی بنویسید میتوانید یک تابع پایتون هم برای محاسبه آن بنویسید. قدم اول این است که تصمیم بگیرید پارامترها چه باید باشند. در این مورد این باید واضح باشد که تابع فاکتوریل فقط integer میگیرد.
def factorial(n)
اگر مقدار n برابر با ۰ باشد تمام کاری که ما باید انجام دهیم این است که ۱ را برگردانیم.
def factorial(n):
if n == 0:
return 1
در غیر اینصورت، و در جالب ترین قسمت آن ما باید یک فراخوانی بازگشتی برای پیدا کردن فاکتوریل n-1 بنویسیم و بعد آن را در n ضرب کنیم:
def factorial(n):
if n == 0:
return 1
else:
recurse = factorial(n-1)
result = n * recurse
return result
جریان اجرای این برنامه همان جریان اجرای شمارش معکوس در قسمت ۵.۸ است. اگر ما فاکتوریل را با مقدار ۳ فراخوانی کنیم:
چون ۳ برابر نیست با ۰، به شاخه دوم رفته و فاکتوریل n-1 را محاسبه میکنیم.
چون ۲ برابر نیست با ۰، به شاخه دوم رفته و فاکتوریل n-1 را محاسبه میکنیم.
چون ۱ برابر نیست با ۰، به شاخه دوم رفته و فاکتوریل n-1 را محاسبه میکنیم.
چون ۰ برابر است با ۰، در شاخه اول مقدار یک را برمی گرداند بدون اینکه فراخوانی بازگشتی دیگری انجام دهد.
مقدار بازگردانده برابر است با ۱ و در n ضرب میشود که همان ۲ است و مقدار باز میگردد.
مقدار باز گردانده شده برابر است با ۲ که در n ضرب می شود و نتیجه برابر است با ۶ و برابر است با مقدار بازگشتی همان تابعی که کل فرأیند برنامه را آغاز کرده است.
شکل 6.1 : شکل پشته را برای اجرای دنباله این تابع نشان می دهد.
مقادیر بازگشتی نشان داده شده در هر مرحله به پشته بالایی فرستاده می شود. در هر پشته مقدار بازگشتی برابر با مقدار متغیر result است که از حاصل ضرب n در recurse به دست می آید.
#### 6.6 leap of faith اعتماد عمیق
یک راه برای خواندن برنامه پیگیری جریان اجرای آن است اما اینکار می تواند به سرعت سخت و فشارآور باشد.راه حل جایگزین که من آن را اعتماد عمیق مینامم این است که وقتی شما در طول پیگیری روند اجرا به فراخوانی یک تابع رسیدید فرض کنید که تابع به درستی کار میکند و نتیجه صحیح را برمیگرداند.
در واقع شما روش اعتماد عمیق را قبلا و در هنگام استفاده از توابع درونی سازی شده پایتون را تمرین کرده اید. وقتی شما math.cos یا math.exp را فراخوانی میکنید، به بدنه اصلی توابع کاری ندارید و فقط فرض می کنید که آنها درست کار میکنند زیرا افرادی که آنها را نوشته اند برنامه نویسان خوبی بوده اند.
این روال هنگامی که شما یکی از توابعی که خودتان نوشته اید را فراخوانی میکنید هم درست است برای مثال در قسمت 6.5 ما یک تابع به عنوان is_divisible نوشتیم که نشان میدهد آیا یک عدد بر دیگری بخش پذیری است یا خیر. از آنجایی که ما مطمئن شدیم این تابع درست کار میکند (با استفاده از تست و آزمایش کد) میتوانیم از آن بدون بررسی بدنه تابع در برنامه خود استفاده کنیم.
همین روال در برنامه های بازگشتی هم صادق است. وقتی شما یک فراخوانی بازگشتی دریافت میکنید، به جای پیگیری جریان اجرا، باید فرض کنید فراخوانی بازگشتی درست کار میکند(مقدار درست را بازمیگرداند) و شما باید از خودتان بپرسید : « فرض میکنیم که من میتوانم فاکتوریل n-1 را بدست آورم، حال آیا میتوان با استفاده از آن فاکتوریل n را پیدا کرد؟»
در واقع، این کمی عجیب به نظر میرسد که شما فرض کنید یک تابع درست کار میکند در حالیکه شما هنوز نوشتن آن را تمام نکرده اید، اما این همان دلیلی است که من نام روش را اعتماد عمیق گذاشته ام.
#### 6.7 یک مثال بیشتر
بعد از فاکتوریل، فیبوناتچی رایج ترین مثال توابع بازگشتی تعریف شده در ریاضیات است، که تعریف آن بصورت زیر است: ( برای اطلاعات بیشتر http://en.wikipedia.org/wiki/Fibonacci_number را ببینید):
fibonacci(0) = 0
fibonacci(1) = 1
fibonacci(n) = fibonacci(n-1) + fibonacci(n-2)
در زبان پایتون به صورت زیر ترجمه میکند:
def fibonacci(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fibonacci(n-1) + fibonacci(n-2)
حال اگر سعی کنید که جریان اجرا را در تابع بالا پیگیری کنید، حتی برای مقادیر کوچک n هم کار سختی در پیش خواهید داشت. اما طبق اصل اعتماد عمیق، اگر شما فرض کنید که دو فراخوانی بازگشتی درست کار میکنند میتوانید به راحتی پاسخ درست را با جمع کردن حاصل آنها با یکدیگر به دست آورید.
#### 6.8 کنترل نوع
اگر ما تابع factorial را با مقدار 1.5 به عنوان آرگومان فراخوانی کنیم چه اتفاقی می افتد؟
>>>factorial (1.5)
تبدیل به یک تابع بازگشتی نامحدود میشود. اما چرا؟
تابع یک وضعیت پایه را کنترل میکند(وقتی n == 0 ) اما در حالت بالا n از نوع integer نیست پس ما هیچگاه به وضعیت پایه نمیرسیم و برنامه برای همیشه بازگشتی میماند.
در اولین فراخوانی بازگشتی مقدار n برابر با 0.5 خواهد بود در فراخوانی بعدی -0.5 و از اینجا به بعد همینطور کوچکتر (منفی تر) میشود که هیچگاه به 0 نمیرسد.
ما دو انتخاب داریم، اولین اینکه تابع factorial را تعمیم دهیم تا با مقادیر اعشاری هم کار کند و یا تابع را طوری تغییر دهیم که نوع آرگومان خود را کنترل کند.روش اول گاما نامیده می شود که کمی فراتر از اهداف این کتاب است بنابراین به سراغ روش دوم می رویم.
میتوانیم از تابع درونی سازی شده isinstance برای تایید نوع آرگومان تابع استفاده کنیم. و به همین صورت میتوانیم مطمئن شویم که آیا مقدار آرگومان مثبت است یا خیر:
def factorial(n):
if not isinstance(n, int):
print (‘Factorial is only defined for integers.’)
return None
elif n < 0:
print (‘Factorial is not defined for negative integers.’)
return None
elif n == 0:
return 1
else:
return n * factorial(n-1)
حالت اول مقادیر غیرصحیح یا noninteger و حالت دوم مقادیر منفی را کنترل میکند. در هر دو حالت برنامه یک پیغام خطا را چاپ میکند و None را برمیگرداند تا نشان دهد مشکلی بوجود آمده است:
>>> factorial('fred')
Factorial is only defined for integers.
None
>>> factorial(-2)
Factorial is not defined for negative integers.
None
اگر ما از هردو مرحله بگذریم، مطمئن میشویم که n مثبت یا صفر است، پس میتوانیم مطمئن باشیم که بازگشت ها خاتمه خواهد یافت و نامحدود نیست.
این برنامه الگویی را نشان میدهد که گاهاً سرپرست یا نگهبان نامیده میشود. دو شرط ابتدایی برنامه به عنوان نگهبان عمل میکنند و از برنامه در برابر مقادیری که ممکن است باعث بروز خطا شوند محافظت میکنند.
در قسمت 11.4 ما جایگزین های منعطف تری برای چاپ خطا خواهیم دید که raising an exception نام دارد.
####
#### 6.9 خطایابی
خرد کردن برنامه به تعدادی تابع کوچکتر باعث بوجود آمدن تعدادی checkpoint برای خطایابی میشود.
اگر یک تابع کار نکرد، سه امکان برای بررسی وجود دارد:
- یک مشکلی در آرگومان هایی که تابع دریافت میکند به وجود آمده است، یک پیش شرط نقض شده است.
- یک مشکلی در خود تابع بوجود آمده است. یک شرط بعدی نقض شده است.
- یک مشکلی در مقدار بازگشتی یا روش استفاده از تابع بوجود آمده است.
برای جلوگیری از حالت اول، شما میتوانید یک عبارت print به ابتدای تابع اضافه کنید و مقدار( و یا شاید نوع) آرگومان های ورودی را در خروجی نشان دهید. یا تکه کدی بنویسید که صرفا پیش شرط ها را کنترل کند.
اگر همه پیش شرط ها درست هستند، یک عبارت پرینت قبل از هر دستور return اضافه کنید تا مقدار بازگشتی را چاپ کند. در صورت امکان حاصل را دستی حساب کنید و با خروجی تابع مقایسه کنید.تابع را با مقادیری در نظر بگیرید که چک کردن حاصل آن ساده باشد.( همانند کاری که در قسمت 6.2 انجام دادند)
اگر به نظر رسید که تابع درست کار میکند، نگاهی به فراخوانی تابع بیاندازید و مطمئن شوید که مقدار بازگشتی به درستی مورد استفاده قرار گرفته است (یا چک کنید که اصلا استفاده میشود یا نه!).
اضافه کردن یک عبارت print به ابتدا و انتهای تابع میتواند به کنترل روند اجرای برنامه کمک و آن را بیشتر قابل مشاهده کند. برای مثال، نسخه تابع factorial به همراه عبارات print به صورت مقابل است:
def factorial(n):
space = ‘ ‘ * (4*n)
print (space, ‘factorial’, n)
if n == 0:
print (space, ‘returning 1’)
return 1
else:
recurse = factorial(n-1)
result = n * recurse
print(space, ‘returning’, result)
return result
متغیر space یک رشته از کاراکترهای فاصله است که تو رفتگی خروجی ها را کنترل می کند، در ادامه خروجی factorial(4) را داریم:
factorial 4
factorial 3
factorial 2
factorial 1
factorial 0
returning 1
returning 2
returning 4
returning 6
returning 24
اگر در بررسی روند اجرا گمراه شدید این روش خروجی گرفتن میتواند بسیار مفید باشد. هرچند ممکن است طراحی و توسعه داربست برنامه کمی وقت شما را بگیرد اما قسمت کوچکی از داربست میتواند مقدار زیادی از زمان خطایابی ها را ذخیره کند.
#### 6.10 فهرست لغات
متغیرهای موقتی: متغیری که برای ذخیره مقادیر واسط در محاسبات پیچیده استفاده میشود.
کد بلا استفاده (dead code): قسمتی از یک برنامه که هیچوقت اجرا نخواهد شد، اغلب به این دلیل که بعد از عبارت return نوشته می شوند.
توسعه تکاملی: یک روش برنامه نویسی است که برای جلوی جلوگیری از خطایابی طراحی شده، به صورتی که در هر بار اجرا فقط یک تکه کد به برنامه اضافه میشود.
کد داربست: کدی که در طول فرآیند توسعه برنامه استفاده میشود اما قسمتی از برنامه نیست.
نگهبان (guardians) : الگویی از برنامه نویسی که از عبارات شرطی برای کنترل و مدیریت شرایطی که ممکن است سبب بروز خطا شوند استفاده میکند.
#### 6.11 تمرینات
تمرین 6.1: یک نمای پشته برای برنامه زیر بکشید، این برنامه چه چیزی را چاپ میکند؟
def b(z):
prod = a(z, z)
print (z, prod)
return prod
def a(x,y):
x = x + 1
return X * y
def c(x, y, z):
total = x + y + z
square = b(total)**2
return square
x = 1
y = x + 1
print (c(x, y+3, x+y))
تمرین 6.2: تابع آکرمن،( A(m, n به صورت مقابل تعریف شده است:
(http: // en. wikipedia. org/ wiki/ Ackermann_ function را مشاهده کنید)
تابعی به نام ack بنویسید که مانند تابع آکرمن فعالیت میکند. تابع خود را با ack(3, 4) بیازمایید نتیجه باید 125 باشد. برای مقادیر بزرگتر m و n چه اتفاقی خواهد افتاد؟ پاسخ در : http: // thinkpython2. com/ code/
ackermann. py .
تمرین 6.3: کلمات دوسر یا دوسو به کلماتی میگویند که ابتدا و اتنهای آنها یکسان است کلماتی مانند noon یا redivider از این نوع هستند. اگر تعریف بالا را بصورت بازگشتی تعریف کنیم به این صورت خواهد بود:
کلمات دوسو کلماتی هستند که حروف ابتدایی و انتهایی آنها یکسان بوده و حروف میانی آن دوسو است.
کدهای زیر توابعی هستند که یک کلمه را دریافت کرده و حروف ابتدا، انتها و میانی آن را برمی گردانند.
def first(word):
return word[0]
def last(word):
return word[-1]
def middle(word):
return word[1:-1]
در فصل هشتم نحوه عملکرد این توابع را خواهیم دید.
- این توابع را در فایلی به نام palindrome.py بنویسید و آنها را آزمایش کنید. اگر تابع middle را با یک کلمه دو حرفی فراخوانی کنید چه اتفاقی میافتد؟ با یک کلمه یک حرفی چطور؟ اگر آرگومان ارسالی به آن خالی و به صورت " " باشد خروجی چیست؟
- تابعی به نام is_palindrome بنویسید که یک رشته را به عنوان آرگومان میگیرد و اگر رشته دوسو بود مقدار True و در غیر اینصورت False را بازمیگرداند. به یاد داشته باشید که شما میتوانید از تابع درونی len برای شمارش طول رشته اضافه کنید.
راه حل در: http: // thinkpython2. com/ code/ palindrome_ soln. py
تمرین 6.4 :
تمرین 6.5: بزرگترین مقسوم علیه مشترک a و b، بزرگترین عددی است که بر هردو عدد تقسیم میشود و باقی مانده ندارد.
یک راه حل برای پیدا کردن بزرگترین مقسوم علیه مشترک روش تقسیم متوالی است به این صورت که r را باقی مانده تقسیم در نظر میگیریم، حال داریم:
gcd(a, b) = gcd(b, r)
در مرحله بعد نیز باقی مانده تقسیم b بر r را به دست می آوریم و مقدار بزرگترین مقسوم علیه مشترک آنرا با r بدست می آوریم. تا وقتی که باقی مانده تقسیم برابر با 0 شود. که در این صورت داریم gcd(a, 0) = 0
تابعی به نام GCD بنویسید که دو عدد a و b را میگیرد و مقسوم علیه مشترک آن ها را بازمیگرداند.
credit: این تمرین مثالی از کتاب ساختار و تعبیری از برنامه های کامپیوتری نوشته آبلسون و ساوسمن است.